package com.pachong.android.frameworkbase.utils.zip;

import android.app.Activity;
import android.content.Context;
import android.util.Log;

import com.pachong.android.frameworkbase.utils.MultiCardUtils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;


/**
 * 解压器，用来解压压缩文件的工具类。
 * 
 * 用户可以通过设置OnExtractListener来监听解压缩的过程
 * 
 * @author Vivi Peng
 * 
 * @date 2015-01-14
 */
public class ZipExtractor {

	private static final String TAG = ZipExtractor.class.getSimpleName();
	private final Context mContext;
	private final File mInput;
	private final File mOutput;
	private long mCurrSize = 0; // 当前已解压的大小
	private long mTotalSize = 0; // 解压文件的总大小

	public static final int ERR_NO_ENOUGH_SPACE = 0x11; // 存储空间不足
	public static final int ERR_EXTRACT = 0x12; // 解析异常
	public static final int ERR_INVALID_ZIP_FILE = 0x13; // 无效的Zip文件
	public static final int ERR_FILE_NOT_FOUND = 0x14; // 找不到待解压的文件
	public static final int ERR_UNKONWN = 0x15;

	/**
	 * 取消的标识
	 */
	private boolean mCanceled = false;
	/**
	 * 是否在解压缩
	 */
	private boolean mIsExcuting = false;

	private OnExtractListener mOnExtractListener;

	/*
	 * public void setOnExtractorListener(OnExtractorListener listener) {
	 * mExtractorListener = listener; }
	 */

	/**
	 * 
	 * @param in
	 *            输入文件的绝对路径
	 * @param out
	 *            输出文件的绝对路径
	 * @param context
	 * @param listener
	 *            解压缩监听器
	 */
	public ZipExtractor(Context context, String in, String out,
			OnExtractListener listener) {
		super();
		mInput = new File(in);
		mOutput = new File(out);
		if (!mOutput.exists()) {
			if (!mOutput.mkdirs()) {
				Log.e(TAG,
						"Failed to make directories:"
								+ mOutput.getAbsolutePath());
			}
		}
		mOnExtractListener = listener;
		mContext = context;
	}

	public void execute() {
		if (mIsExcuting) {
			Log.w(TAG, "File is extracting... : " + mInput.getAbsolutePath());
			return;
		}

		new Thread() {

			@Override
			public void run() {
				unzip();
				super.run();
			}

		}.start();
	}

	/**
	 * 取消解压缩
	 */
	public void cancel() {
		if (mIsExcuting) {
			mCanceled = true;
		}
	}

	private long unzip() {
		mIsExcuting = true;
		ZipFile zip = null;
		mCurrSize = 0;
		long availableSpace = MultiCardUtils.getInstance(mContext)
				.getExternalSDCardSpace();
		try {
			zip = new ZipFile(mInput);
			mTotalSize = getOriginalSize(zip);
			if (mOnExtractListener != null) {
				mOnExtractListener.onCaculateTotalSize(mTotalSize);
			}
			if (mTotalSize > availableSpace) {
				return mCurrSize;
			}
			Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) zip
					.entries();
			while (!mCanceled && entries.hasMoreElements()) {
				ZipEntry entry = entries.nextElement();
				if (entry.isDirectory()) {
					continue;
				}
				File destination = new File(mOutput, entry.getName());
				if (!destination.getParentFile().exists()) {
					Log.e(TAG, "make="
							+ destination.getParentFile().getAbsolutePath());
					destination.getParentFile().mkdirs();
				}
				if (destination.exists() && mContext != null) {

				}
				ProgressReportingOutputStream outStream = new ProgressReportingOutputStream(
						destination);
				copy(zip.getInputStream(entry), outStream);
				outStream.close();
			}

		} catch (ZipException e) {
			onFailure(ERR_INVALID_ZIP_FILE);
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			onFailure(ERR_FILE_NOT_FOUND);
			e.printStackTrace();
		} catch (IOException e) {
			onFailure(ERR_UNKONWN);
			e.printStackTrace();
		} finally {
			if (mTotalSize > 0 && mTotalSize == mCurrSize) {
				onSuccess();
			} else if (mTotalSize > 0 && mTotalSize > mCurrSize) {
				onFailure(ERR_EXTRACT);
			}

			if (mTotalSize > availableSpace) {
				onFailure(ERR_NO_ENOUGH_SPACE);
			}

			mIsExcuting = false;
			mCanceled = false;
			try {
				if (zip != null) {
					zip.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return mCurrSize;
	}

	private void onSuccess() {
		if (mOnExtractListener != null) {
			mOnExtractListener.onComplete(mTotalSize);
		}
	}

	private void onFailure(final int errorCode) {
		if (mOnExtractListener != null) {
			mOnExtractListener.onFailure(errorCode, mTotalSize, mCurrSize);
		}

	}

	private long getOriginalSize(ZipFile file) {
		Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) file.entries();
		long originalSize = 0l;
		while (entries.hasMoreElements()) {
			ZipEntry entry = entries.nextElement();
			if (entry.getSize() >= 0) {
				originalSize += entry.getSize();
			}
		}
		return originalSize;
	}

	private int copy(InputStream input, OutputStream output) {
		byte[] buffer = new byte[1024 * 8];
		BufferedInputStream in = new BufferedInputStream(input, 1024 * 8);
		BufferedOutputStream out = new BufferedOutputStream(output, 1024 * 8);
		int count = 0, n = 0;
		try {
			while (!mCanceled && (n = in.read(buffer, 0, 1024 * 8)) != -1) {
				out.write(buffer, 0, n);
				count += n;
			}
			out.flush();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return count;
	}

	private final class ProgressReportingOutputStream extends FileOutputStream {

		public ProgressReportingOutputStream(File file)
				throws FileNotFoundException {
			super(file);
		}

		@Override
		public void write(byte[] buffer, int byteOffset, int byteCount)
				throws IOException {
			super.write(buffer, byteOffset, byteCount);
			if (byteCount == 0) {
				return;
			}
			mCurrSize += byteCount;
			if (mOnExtractListener != null) {
				if (mContext instanceof Activity) {
					Activity act = (Activity) mContext;
					mMainUiRannable.currSize = mCurrSize;
					act.runOnUiThread(mMainUiRannable);
				} else {
					mOnExtractListener.onProgressUpdate(mCurrSize);
				}
			}
		}

	}

	public class MainUiRannable implements Runnable {

		public long currSize = 0;
		public long oldSize = 0;

		public void run() {
			if (currSize == oldSize) {
				return;
			} else {
				oldSize = currSize;
				mOnExtractListener.onProgressUpdate(currSize);
			}
		}
	}

	MainUiRannable mMainUiRannable = new MainUiRannable();

	/*
	 * Runnable mMainUiRannable = new Runnable() {
	 * 
	 * 
	 * 
	 * @Override public void run() {
	 * mOnExtractListener.onProgressUpdate(currSize); } };
	 */

	/**
	 * 解压缩的接口，包含解压缩过程中成功、失败、获取当前解压文件大小等事件
	 * 
	 * @author Vivi Peng
	 * 
	 * @date 2015-01-07
	 * 
	 */
	public interface OnExtractListener {

		/**
		 * 解压进度
		 * 
		 * @param currSize
		 *            当前解压的数据大小
		 */
		void onProgressUpdate(long currSize);

		/**
		 * 解压缩完成
		 * 
		 * @param totalSize
		 */
		void onComplete(long totalSize);

		/**
		 * 解压缩失败
		 * 
		 * @param totalSize
		 * @param currSize
		 *            失败之前已经解压的包的大小
		 */
		void onFailure(int errorCode, long totalSize, long currSize);

		/**
		 * 计算总大小
		 * 
		 * @param totalSize
		 *            压缩包的大小
		 */
		void onCaculateTotalSize(long totalSize);

	}
}
